package cn.st4rlight.util.execute;

import java.time.Duration;
import java.util.Objects;

import com.github.phantomthief.util.ThrowableRunnable;
import com.github.phantomthief.util.ThrowableSupplier;
import com.google.common.util.concurrent.Uninterruptibles;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 带重试执行工具类
 *
 * @author st4rlight <st4rlight@163.com>
 * Created on 2024-05-05
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RetryUtil {

    /**
     * 带重试执行
     *
     * @param maxRetryTimes 重试次数
     * @param sleepDuration 重试间隔
     */
    public static <X extends Throwable> void runWithRetry(
        int maxRetryTimes, Duration sleepDuration, ThrowableRunnable<X> function
    ) throws X {
        supplyWithRetry(maxRetryTimes, sleepDuration, () -> {
            function.run();
            return null;
        });
    }


    /**
     * 带重试执行
     *
     * @param maxRetryTimes 重试次数
     * @param sleepDuration 重试间隔
     */
    @SuppressWarnings("unchecked")
    public static <T, X extends Throwable> T supplyWithRetry(
        int maxRetryTimes, Duration sleepDuration, ThrowableSupplier<T, X> supplier
    ) throws X {
        int times = 0;

        Throwable lastThrowable = null;
        while (times <= maxRetryTimes) {
            try {
                return supplier.get();
            } catch (Exception ex) {
                if (Objects.nonNull(sleepDuration)) {
                    Uninterruptibles.sleepUninterruptibly(sleepDuration);
                }
                times++;
                lastThrowable = ex;
            }
        }

        if (Objects.nonNull(lastThrowable)) {
            throw (X) lastThrowable;
        }
        return null;
    }
}
